<html>

<head>
	<meta charset="utf-8">
	<title>Tree</title>
	<style>
		#container {
			width: 400;
			height: 600;
			border: 1px solid black;
		}

		.monaco-scrollable-element>.scrollbar>.slider {
			background: rgba(100, 100, 100, .4);
		}

		.tl-contents {
			flex: 1;
		}

		.monaco-list-row:hover:not(.selected):not(.focused) {
			background: gainsboro !important;
		}
	</style>
</head>

<body>
	<input type="text" id="filter" />
	<button id="expandall">Expand All</button>
	<button id="collapseall">Collapse All</button>
	<button id="renderwidth">Render Width</button>
	<button id="refresh">Refresh</button>
	<div id="container"></div>

	<script src="/static/vs/loader.js"></script>
	<script>
		function perf(name, fn) {
			performance.mark('before ' + name);
			const start = performance.now();
			fn();
			console.log(name + ' took', performance.now() - start);
			performance.mark('after ' + name);
		}

		require.config({ baseUrl: '/static' });

		require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/objectTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { CompressibleObjectTree }, { AsyncDataTree }, { DataTree }, { TreeVisibility }, { iter }) => {
			function createIndexTree(opts) {
				opts = opts || {};

				const delegate = {
					getHeight() { return 22; },
					getTemplateId() { return 'template'; },
					hasDynamicHeight() { return true; }
				};

				const renderer = {
					templateId: 'template',
					renderTemplate(container) { return container; },
					renderElement(element, index, container) {
						if (opts.supportDynamicHeights) {
							let v = [];
							for (let i = 1; i <= 5; i++) {
								v.push(element.element);
							}
							container.innerHTML = v.join('<br />');
						} else {
							container.innerHTML = element.element;
						}
					},
					disposeElement() { },
					disposeTemplate() { }
				};

				const treeFilter = new class {
					constructor() {
						this.pattern = null;
						let timeout;
						filter.oninput = () => {
							clearTimeout(timeout);
							timeout = setTimeout(() => this.updatePattern(), 300);
						};
					}
					updatePattern() {
						if (!filter.value) {
							this.pattern = null;
						} else {
							this.pattern = new RegExp(filter.value, 'i');
						}

						perf('refilter', () => tree.refilter());
					}
					filter(el) {
						return (this.pattern ? this.pattern.test(el) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
					}
				};

				const tree = new IndexTree('test', container, delegate, [renderer], null, { ...opts, filter: treeFilter, setRowLineHeight: false });

				return { tree, treeFilter };
			}

			function createCompressedObjectTree(opts) {
				opts = opts || {};

				const delegate = {
					getHeight() { return 22; },
					getTemplateId() { return 'template'; },
					hasDynamicHeight() { return true; }
				};

				const renderer = {
					templateId: 'template',
					renderTemplate(container) { return container; },
					renderElement(element, index, container) {
						container.innerHTML = element.element.name;
					},
					renderCompressedElements(node, index, container, height) {
						container.innerHTML = `🙈 ${node.element.elements.map(el => el.name).join('/')}`;
					},
					disposeElement() { },
					disposeTemplate() { }
				};

				const treeFilter = new class {
					constructor() {
						this.pattern = null;
						let timeout;
						filter.oninput = () => {
							clearTimeout(timeout);
							timeout = setTimeout(() => this.updatePattern(), 300);
						};
					}
					updatePattern() {
						if (!filter.value) {
							this.pattern = null;
						} else {
							this.pattern = new RegExp(filter.value, 'i');
						}

						perf('refilter', () => tree.refilter());
					}
					filter(el) {
						return (this.pattern ? this.pattern.test(el) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
					}
				};

				const tree = new CompressibleObjectTree('test', container, delegate, [renderer], { ...opts, filter: treeFilter, setRowLineHeight: false, collapseByDefault: true, setRowLineHeight: true });

				return { tree, treeFilter };
			}

			function createAsyncDataTree() {
				const delegate = {
					getHeight() { return 22; },
					getTemplateId() { return 'template'; }
				};

				const renderer = {
					templateId: 'template',
					renderTemplate(container) { return container; },
					renderElement(node, index, container) { container.textContent = node.element.element.name; },
					disposeElement() { },
					disposeTemplate() { }
				};

				const treeFilter = new class {
					constructor() {
						this.pattern = null;
						let timeout;
						filter.oninput = () => {
							clearTimeout(timeout);
							timeout = setTimeout(() => this.updatePattern(), 300);
						};
					}

					updatePattern() {
						if (!filter.value) {
							this.pattern = null;
						} else {
							this.pattern = new RegExp(filter.value, 'i');
						}

						perf('refilter', () => tree.refilter());
					}
					filter(el) {
						return (this.pattern ? this.pattern.test(el.name) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
					}
				};

				const sorter = new class {
					compare(a, b) {
						if (a.collapsible === b.collapsible) {
							return a.name < b.name ? -1 : 1;
						}

						return a.collapsible ? -1 : 1;
					}
				};

				const dataSource = new class {
					hasChildren(element) {
						return element === null || element.element.type === 'dir';
					}
					getChildren(element) {
						return new Promise((c, e) => {
							const xhr = new XMLHttpRequest();
							xhr.open('GET', element ? `/api/readdir?path=${element.element.path}` : '/api/readdir');
							xhr.send();
							xhr.onreadystatechange = function () {
								if (this.readyState == 4 && this.status == 200) {
									const els = JSON.parse(this.responseText).map(element => ({
										element,
										collapsible: element.type === 'dir'
									}));

									c(els);
								}
							};
						});
					}
				}

				const identityProvider = {
					getId(node) {
						return node.element.path;
					}
				};

				const tree = new AsyncDataTree('test', container, delegate, [renderer], dataSource, { filter: treeFilter, sorter, identityProvider });

				return { tree, treeFilter };
			}

			function createDataTree() {
				const delegate = {
					getHeight() { return 22; },
					getTemplateId() { return 'template'; }
				};

				const renderer = {
					templateId: 'template',
					renderTemplate(container) { return container; },
					renderElement(node, index, container) { container.textContent = node.element.name; },
					disposeElement() { },
					disposeTemplate() { }
				};

				const treeFilter = new class {
					constructor() {
						this.pattern = null;
						let timeout;
						filter.oninput = () => {
							clearTimeout(timeout);
							timeout = setTimeout(() => this.updatePattern(), 300);
						};
					}

					updatePattern() {
						if (!filter.value) {
							this.pattern = null;
						} else {
							this.pattern = new RegExp(filter.value, 'i');
						}

						perf('refilter', () => tree.refilter());
					}
					filter(el) {
						return (this.pattern ? this.pattern.test(el.name) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
					}
				};

				const dataSource = new class {
					getChildren(element) {
						return element.children || [];
					}
				};

				const identityProvider = {
					getId(node) {
						return node.name;
					}
				};

				const tree = new DataTree('test', container, delegate, [renderer], dataSource, { filter: treeFilter, identityProvider });

				tree.setInput({
					children: [
						{ name: 'A', children: [{ name: 'AA' }, { name: 'AB' }] },
						{ name: 'B', children: [{ name: 'BA', children: [{ name: 'BAA' }] }, { name: 'BB' }] },
						{ name: 'C' }
					]
				});

				return { tree, treeFilter };
			}

			switch (location.search) {
				case '?problems': {
					const { tree, treeFilter } = createIndexTree();

					expandall.onclick = () => perf('expand all', () => tree.expandAll());
					collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
					renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));

					const files = [];
					for (let i = 0; i < 100000; i++) {
						const errors = [];

						for (let j = 1; j <= 3; j++) {
							errors.push({ element: `error #${j} ` });
						}

						files.push({ element: `file #${i}`, children: errors });
					}

					perf('splice', () => tree.splice([0], 0, files));
					break;
				}
				case '?data': {
					const { tree, treeFilter } = createAsyncDataTree();

					expandall.onclick = () => perf('expand all', () => tree.expandAll());
					collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
					renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
					refresh.onclick = () => perf('refresh', () => tree.updateChildren());

					tree.setInput(null);

					break;
				}
				case '?objectdata': {
					const { tree, treeFilter } = createDataTree();

					expandall.onclick = () => perf('expand all', () => tree.expandAll());
					collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
					renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
					refresh.onclick = () => perf('refresh', () => tree.updateChildren());

					break;
				}
				case '?compressed': {
					const { tree, treeFilter } = createCompressedObjectTree();

					expandall.onclick = () => perf('expand all', () => tree.expandAll());
					collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());

					const xhr = new XMLHttpRequest();
					xhr.open('GET', '/compressed.json');
					xhr.send();
					xhr.onreadystatechange = function () {
						if (this.readyState == 4 && this.status == 200) {
							tree.setChildren(null, JSON.parse(this.responseText));
						}
					};

					break;
				}
				case '?height': {
					const { tree, treeFilter } = createIndexTree({ supportDynamicHeights: true });

					expandall.onclick = () => perf('expand all', () => tree.expandAll());
					collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
					renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));

					const xhr = new XMLHttpRequest();
					xhr.open('GET', '/api/ls?path=');
					xhr.send();
					xhr.onreadystatechange = function () {
						if (this.readyState == 4 && this.status == 200) {
							perf('splice', () => tree.splice([0], 0, [JSON.parse(this.responseText)]));
							treeFilter.updatePattern();
						}
					};

					// container.


					break;
				}
				default: {
					const { tree, treeFilter } = createIndexTree();

					expandall.onclick = () => perf('expand all', () => tree.expandAll());
					collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
					renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));

					const xhr = new XMLHttpRequest();
					xhr.open('GET', '/api/ls?path=');
					xhr.send();
					xhr.onreadystatechange = function () {
						if (this.readyState == 4 && this.status == 200) {
							perf('splice', () => tree.splice([0], 0, [JSON.parse(this.responseText)]));
							treeFilter.updatePattern();
						}
					};
				}
			}
		});
	</script>
</body>

</html>
